﻿using System.Data;
using Devonline.AspNetCore;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.Identity;

/// <summary>
/// 用户认证授权相关基础服务
/// </summary>
public class AuthorizationService
{
    private const string CONTROLLER = "controller";
    private const string ACTION = "action";
    private readonly AppSetting _appSetting;
    private readonly ILogger<AuthorizationService> _logger;
    private readonly IDistributedCache _cache;
    private readonly IdentityDbContext _context;
    private readonly HttpContext _httpContext;
    private readonly HttpRequest _request;
    private readonly RouteValueDictionary _routeValues;
    private readonly DistributedCacheEntryOptions _cacheEntryOptions;

    public AuthorizationService(
        AppSetting appSetting,
        ILogger<AuthorizationService> logger,
        IHttpContextAccessor httpContext,
        IDistributedCache cache,
        IdentityDbContext context
        )
    {
        ArgumentNullException.ThrowIfNull(httpContext.HttpContext);
        _appSetting = appSetting;
        _logger = logger;
        _httpContext = httpContext.HttpContext;
        _request = _httpContext.Request;
        _cache = cache;
        _context = context;
        _cacheEntryOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(_appSetting.CacheExpireTime) };
        _routeValues = _request.RouteValues;
    }

    /// <summary>
    /// 授权判断
    /// </summary>
    /// <param name="userName">用户名</param>
    /// <param name="resource">资源</param>
    /// <returns></returns>
    public async Task<bool> AuthorizeAsync(string? userName = default, string? resource = default)
    {
        var userContext = await GetUserContextAsync(userName);
        if (userContext is null || userContext.User is null)
        {
            _logger.LogDebug($"user {userName ?? userContext?.UserName ?? AppSettings.USER_ANONYMOUS} can not get user access resources!");
            return false;
        }

        var viewModel = GetPermissionResource(userContext, resource);
        if (viewModel == null)
        {
            _logger.LogDebug($"user {userContext.UserName} can not get permission access resources!");
            return false;
        }

        if (viewModel.AccessRules is not null && viewModel.AccessRules.Count != 0)
        {
            foreach (var accessRule in viewModel.AccessRules)
            {
                if (!AuthorizeAccessRule(userContext, accessRule))
                {
                    _logger.LogDebug($"user {userContext.UserName} request resource access rule validate failed!");
                    return false;
                }
            }
        }

        return true;
    }

    /// <summary>
    /// 根据身份类型和编号获取对应的用户
    /// </summary>
    /// <param name="identityType">身份分配类型</param>
    /// <param name="identities">身份编号</param>
    public async Task<ICollection<User>?> GetIdentityUsers(IdentityType identityType = IdentityType.User, params string[] identities)
    {
        if (identities is null || identities.Length == 0)
        {
            return null;
        }

        return identityType switch
        {
            IdentityType.User => await _context.Users.Where(x => x.State == DataState.Available && identities.Contains(x.Id) || identities.Contains(x.UserName)).ToListAsync(),
            IdentityType.Role => await _context.Users.Where(x => x.State == DataState.Available && _context.UserRoles.Where(a => identities.Contains(a.RoleId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Group => await _context.Users.Where(x => x.State == DataState.Available && _context.UserGroups != null && _context.UserGroups.Where(a => identities.Contains(a.GroupId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Level => await _context.Users.Where(x => x.State == DataState.Available && x.LevelId != null && identities.Contains(x.LevelId)).ToListAsync(),
            IdentityType.System => await _context.Users.Where(x => x.State == DataState.Available && x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM).ToListAsync(),
            IdentityType.Anonymous => await _context.Users.Where(x => x.State == DataState.Available && x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS).ToListAsync(),
            _ => await _context.Users.Where(x => x.State == DataState.Available).ToListAsync(),
        };
    }
    /// <summary>
    /// 获取用户上下文
    /// 上下文包含用户所有已分配身份可获取的资源列表及其树形结构
    /// 此阶段不判断 Condition 条件
    /// </summary>
    /// <param name="userName">用户名</param>
    /// <param name="force">是否强制获取</param>
    /// <returns></returns>
    public async Task<UserContext?> GetUserContextAsync(string? userName = default, bool force = false)
    {
        userName ??= _httpContext.GetUserName();
        UserContext? userContext = null;
        if (!force && _appSetting.EnableCache)
        {
            _logger.LogDebug($"get {userName} user context from cache!");
            userContext = _cache.GetValue<UserContext>(AppSettings.CACHE_USER + userName);
        }

        if (!force && userContext is not null)
        {
            return userContext;
        }

        _logger.LogDebug($"get {userName} user context from database!");
        var user = await _context.Users.FirstOrDefaultAsync(x => x.UserName == userName);
        if (user is null || _context.UserGroups is null || _context.Levels is null)
        {
            return null;
        }

        //脱敏
        user.PasswordHash = null;
        user.PhoneNumber = user.PhoneNumber.DesensitizePhoneNumber();

        LevelViewModel? levelViewModel = null;

        //1. 获取用户级别身份
        if (!string.IsNullOrWhiteSpace(user.LevelId))
        {
            _logger.LogDebug($"get {userName} user level from database!");
            var level = await _context.Levels.FindAsync(user.LevelId);
            if (level != null)
            {
                levelViewModel = level.CopyTo<LevelViewModel>();
            }
        }

        userContext = new UserContext
        {
            User = user.CopyTo<UserViewModel>(),
            Roles = new List<RoleViewModel>(),
            Groups = new List<GroupViewModel>(),
            Level = levelViewModel,
            Resources = new List<ResourceViewModel>(),
            ResourceTree = new List<ResourceViewModel>()
        };

        //2. 获取用户角色身份
        var roleIds = await _context.UserRoles.Where(x => x.UserId.Equals(user.Id)).Select(x => x.RoleId).Distinct().ToListAsync();
        if (roleIds.IsNotNullOrEmpty())
        {
            _logger.LogDebug($"get {userName} user roles from database!");
            var roles = await _context.Roles.Where(x => roleIds.Contains(x.Id)).Select(x => new RoleViewModel
            {
                Id = x.Id,
                Alias = x.Alias,
                Image = x.Image,
                Description = x.Description,
                Name = x.Name,
                NormalizedName = x.NormalizedName,
                State = x.State,
                Type = x.Type
            }).ToListAsync();

            if (roles.IsNotNullOrEmpty())
            {
                userContext.Roles.AddRange(roles);
            }
        }

        //3. 获取用户组织身份
        var groupIds = await _context.UserGroups.Where(x => x.UserId != null && x.UserId.Equals(user.Id)).Select(x => x.GroupId).Distinct().ToListAsync();
        if (groupIds is not null && groupIds.Count != 0)
        {
            _logger.LogDebug($"get {userName} user groups from database!");
            foreach (var groupId in groupIds)
            {
                if (groupId is not null)
                {
                    var groups = await _context.GetParentsAsync<Group>(groupId);
                    if (groups.IsNotNullOrEmpty())
                    {
                        userContext.Groups.AddRange(groups.CopyTo<List<GroupViewModel>>());
                    }
                }
            }
        }

        //4. 获取用户访问规则及可访问资源
        var accessRules = await GetUserAccessRulesAsync(userContext);
        var resourcesTree = await GetFullResources(force);
        var resourceViewModels = new List<ResourceViewModel>();
        if (resourcesTree is not null && accessRules is not null)
        {
            foreach (var resource in resourcesTree)
            {
                resourceViewModels.AddRange(GetAccessResources(resource, new List<AccessRuleViewModel>(), accessRules));
            }

            resourceViewModels = resourceViewModels.OrderBy(x => x.Code).ToList();
            CleanResourceTree(resourcesTree);
            userContext.Resources.AddRange(resourceViewModels);
            userContext.ResourceTree.AddRange(resourcesTree);
        }

        if (_appSetting.EnableCache)
        {
            await _cache.SetStringAsync(AppSettings.CACHE_USER + userContext.UserName, userContext.ToJsonString(), _cacheEntryOptions);
        }

        return userContext;
    }
    /// <summary>
    /// 返回用户是否有某个资源的访问权限, 判断数据来自缓存, 判断依据为资源编号或资源内容必须包含在用户可访问资源缓存列表中
    /// </summary>
    /// <param name="resource">资源编号或内容</param>
    /// <param name="userName">用户名</param>
    /// <returns></returns>
    public async Task<bool> UserHasPermissionAsync(string resource, string? userName = default)
    {
        if (string.IsNullOrWhiteSpace(resource))
        {
            return false;
        }

        var userContext = await GetUserContextAsync(userName);
        if (userContext is null || userContext.Resources is null)
        {
            return false;
        }

        resource = resource.ToLowerInvariant();
        return userContext.Resources.Any(x => x.Id.ToLowerInvariant() == resource || (x.Content != null && x.Content.ToLowerInvariant() == resource));
    }

    /// <summary>
    /// 获取当前用户所有已分配的访问规则列表
    /// </summary>
    /// <param name="userContext">认证上下文</param>
    /// <returns></returns>
    private async Task<ICollection<AccessRuleViewModel>?> GetUserAccessRulesAsync(UserContext userContext)
    {
        var identities = new List<string>();
        if (userContext.UserId is not null)
        {
            identities.Add(userContext.UserId);
        }

        if (userContext.Level is not null)
        {
            identities.Add(userContext.Level.Id);
        }

        if (userContext.Roles is not null && userContext.Roles.Count != 0)
        {
            identities.AddRange(userContext.Roles.Select(x => x.Id));
        }

        if (userContext.Groups is not null && userContext.Groups.Count != 0)
        {
            identities.AddRange(userContext.Groups.Select(x => x.Id));
        }

        //获取用户系统身份
        if (userContext.IsSystem)
        {
            //系统中只能有一个 System 类型的用户的用户名叫: AppSettings.USER_SYSTEM 的
            var systemUser = await _context.Users.FirstOrDefaultAsync(x => x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM);
            if (systemUser is not null)
            {
                identities.Add(systemUser.Id);
            }
        }

        //获取匿名用户身份
        //系统中只能有一个 Anonymous 类型的用户, 且用户名叫: AppSettings.USER_ANONYMOUS 的
        var anonymousUser = await _context.Users.FirstOrDefaultAsync(x => x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS);
        if (anonymousUser is not null)
        {
            identities.Add(anonymousUser.Id);
        }

        //查询满足条件的所有访问规则
        if (_context.AccessRules is null)
        {
            return null;
        }

        _logger.LogDebug($"get {userContext.UserName} user access rules from database!");
        return await _context.AccessRules
            .Where(x => x.State == DataState.Available && (!x.ExpireTime.HasValue || x.ExpireTime.Value >= DateTime.Now) && (x.LimitAccessCount == 0 || x.AccessCount <= x.LimitAccessCount) && ((x.IdentityId != null && identities.Contains(x.IdentityId)) || x.IdentityType == IdentityType.All))
            .Select(x => new AccessRuleViewModel
            {
                Id = x.Id,
                AccessCount = x.AccessCount,
                Code = x.Code,
                Condition = x.Condition,
                ExpireTime = x.ExpireTime,
                IdentityId = x.IdentityId,
                IdentityType = x.IdentityType,
                IsAllow = x.IsAllow,
                LimitAccessCount = x.LimitAccessCount,
                Priority = x.Priority,
                ResourceId = x.ResourceId,
                State = x.State
            })
            .ToListAsync();
    }
    /// <summary>
    /// 获取全部的资源列表及其树形结构
    /// </summary>
    /// <param name="force">是否强制获取</param>
    /// <returns></returns>
    private async Task<ICollection<ResourceViewModel>?> GetFullResources(bool force = false)
    {
        List<ResourceViewModel>? resources = null;
        List<ResourceViewModel>? resourcesTree = null;
        var cacheKey = AppSettings.CACHE_APPLICATION + "RESOURCES";
        var treeCacheKey = AppSettings.CACHE_APPLICATION + "RESOURCES_TREE";
        if (!force && _appSetting.EnableCache)
        {
            resources = _cache.GetValue<List<ResourceViewModel>>(cacheKey);
            resourcesTree = _cache.GetValue<List<ResourceViewModel>>(treeCacheKey);
        }

        if (_context.Resources is not null && (force || resources == null || resourcesTree == null))
        {
            resources = (await _context.Resources.Where(x => x.State == DataState.Available).OrderBy(x => x.Code).ToListAsync()).CopyTo<List<ResourceViewModel>>();
            resourcesTree = resources.Where(x => x.ParentId == null).ToList();
            foreach (var resource in resourcesTree)
            {
                GetChildrenResources(resource, resources);
            }

            if (_appSetting.EnableCache)
            {
                await _cache.SetStringAsync(cacheKey, resources.ToJsonString(), _cacheEntryOptions);
                await _cache.SetStringAsync(treeCacheKey, resourcesTree.ToJsonString(), _cacheEntryOptions);
            }
        }

        return resourcesTree;
    }
    /// <summary>
    /// 递归获取并设置资源的子资源
    /// </summary>
    /// <param name="resource"></param>
    /// <param name="resources"></param>
    private static void GetChildrenResources(ResourceViewModel resource, List<ResourceViewModel> resources)
    {
        resource.Children = resources.Where(x => x.ParentId == resource.Id).OrderBy(x => x.Code).ToList();
        if (resource.Children.IsNotNullOrEmpty())
        {
            foreach (var child in resource.Children)
            {
                GetChildrenResources(child, resources);
            }
        }
    }
    /// <summary>
    /// 对权限树中的当前节点及其子节点设置权限
    /// </summary>
    /// <param name="resource">当前资源节点</param>
    /// <param name="accessRules">当前节点拥有的访问规则列表</param>
    /// <param name="fullAccessRules">当前用户获取到的所有访问规则列表</param>
    private static ICollection<ResourceViewModel> GetAccessResources(ResourceViewModel resource, List<AccessRuleViewModel> accessRules, ICollection<AccessRuleViewModel> fullAccessRules)
    {
        var resources = new List<ResourceViewModel>();

        //1. 先获取当前资源满足条件的访问规则
        var currentAccessRules = fullAccessRules.Where(x => x.ResourceId == resource.Id).UnionBy(accessRules, x => x.Id).OrderByDescending(x => x.Priority).ToList();

        //2. 按照规则优先级最高的一条设置当前资源的权限, 如果当前资源没有访问规则，则视为无权限
        resource.HasPermission = currentAccessRules.FirstOrDefault()?.IsAllow == AllowType.Allow;
        if (resource.HasPermission)
        {
            resources.Add(new ResourceViewModel
            {
                Id = resource.Id,
                State = resource.State,
                Name = resource.Name,
                Code = resource.Code,
                Alias = resource.Alias,
                Type = resource.Type,
                Image = resource.Image,
                Title = resource.Title,
                Content = resource.Content,
                ParentId = resource.ParentId,
                Description = resource.Description,
                ResourceType = resource.ResourceType,
                IdentityType = resource.IdentityType,
                OwnerId = resource.OwnerId,
                LevelId = resource.LevelId,
                AccessLevel = resource.AccessLevel,
                HasPermission = resource.HasPermission,
                //TODO 记录当前资源的访问规则会增加系统处理量, 但使用率不高
                AccessRules = currentAccessRules
            });
        }

        //如果当前资源有子资源
        if (resource.Children is not null && resource.Children.Count != 0)
        {
            //3. 且当前资源有权访问, 则递归设置子资源的权限
            foreach (var child in resource.Children)
            {
                resources.AddRange(GetAccessResources(child, currentAccessRules, fullAccessRules));
            }
        }

        return resources.OrderBy(x => x.Code).ToList();
    }
    /// <summary>
    /// 对用户可访问的资源树进行剪枝操作
    /// </summary>
    /// <param name="resources"></param>
    /// <returns></returns>
    private static void CleanResourceTree(ICollection<ResourceViewModel> resources)
    {
        foreach (var resource in resources)
        {
            if (resource.Children is not null && resource.Children.Count != 0)
            {
                CleanResourceTree(resource.Children);
            }
        }

        resources.RemoveWhere(x => !x.HasPermission && (x.Children == null || !x.Children.Any()));
    }
    /// <summary>
    /// 获取授权资源, 未获取到则没有得到授权
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="resource">授权资源</param>
    /// <returns></returns>
    private ResourceViewModel? GetPermissionResource(UserContext userContext, string? resource = default)
    {
        ResourceViewModel? viewModel = null;

        //cond 1. 直接传值的情况
        if (!string.IsNullOrWhiteSpace(resource))
        {
            viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == resource || (x.Content != null && x.Content.ToLowerInvariant() == resource));
            if (viewModel != null)
            {
                _logger.LogDebug($"get {userContext.UserName} user permission for resource {resource} success!");
                return viewModel;
            }
        }

        //cond 2. 判断 request path 的情况
        var requestPath = _request.GetRequestPath().ToLowerInvariant();
        viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == requestPath || (x.Content != null && x.Content.ToLowerInvariant() == requestPath));
        if (viewModel != null)
        {
            _logger.LogDebug($"get {userContext.UserName} user permission for request path {requestPath} success!");
            return viewModel;
        }

        //cond 3. 判断 /api/controller/action 的情况
        var (path, action) = GetPath();
        path = (_request.GetRequestOrigin() + path).ToLowerInvariant();
        viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == path || (x.Content != null && x.Content.ToLowerInvariant() == path));
        if (viewModel != null)
        {
            _logger.LogDebug($"get {userContext.UserName} user permission for route path {path} success!");
            return viewModel;
        }

        //cond 4. 路由参数存在的情况下, 判断 request path 路由变量替换值和删除路由变量值的情况
        if (_routeValues.Count > 2)
        {
            var path1 = requestPath;
            var path2 = requestPath;
            foreach (var route in _routeValues.Where(x => x.Key != CONTROLLER && x.Key != ACTION))
            {
                var routeValue = AppSettings.CHAR_SLASH + (route.Value?.ToString()?.ToLowerInvariant() ?? string.Empty);
                path1 = path1.Replace(routeValue, AppSettings.CHAR_SLASH + "{" + route.Key.ToLowerInvariant() + "}");
                path2 = path2.Replace(routeValue, string.Empty);
            }

            viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == path1 || (x.Content != null && x.Content.ToLowerInvariant() == path1));
            if (viewModel != null)
            {
                _logger.LogDebug($"get {userContext.UserName} user permission for request path and route params {path1} success!");
                return viewModel;
            }

            viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == path2 || (x.Content != null && x.Content.ToLowerInvariant() == path2));
            if (viewModel != null)
            {
                _logger.LogDebug($"get {userContext.UserName} user permission for request path and route params {path2} success!");
                return viewModel;
            }
        }

        //cond 5. 判断 request path 去掉 http method 部分的情况, 因为 path 中可以缺省 action 的部分
        action = action.ToLowerInvariant();
        var _httpMethod = _request.Method.ToLowerInvariant();
        if (_httpMethod == action)
        {
            var method = AppSettings.CHAR_SLASH + action + AppSettings.CHAR_SLASH;
            if (path.Contains(method))
            {
                path = path.Replace(method, string.Empty);
                viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == path || (x.Content != null && x.Content.ToLowerInvariant() == path));
                if (viewModel != null)
                {
                    _logger.LogDebug($"get {userContext.UserName} user permission for request path without http method and / {path} success!");
                    return viewModel;
                }
            }

            method = AppSettings.CHAR_SLASH + action;
            if (path.EndsWith(method))
            {
                path = path.Replace(method, string.Empty);
                viewModel = userContext.Resources.FirstOrDefault(x => x.Id.ToLowerInvariant() == path || (x.Content != null && x.Content.ToLowerInvariant() == path));
                if (viewModel != null)
                {
                    _logger.LogDebug($"get {userContext.UserName} user permission for request path without http method {path} success!");
                    return viewModel;
                }
            }
        }

        _logger.LogInformation($"get {userContext.UserName} user permission for request resource {resource ?? requestPath} in all situation falied!");
        return null;
    }
    /// <summary>
    /// 验证条件授权, 条件授权需要全部满足, 不像 IsAllow 只需最高优先级的满足即可
    /// 目前支持的规则包括两种
    /// 1. @method params..., @ 开头的参数为调用内部方法进行访问授权, 方法的第一个参数不需要填写, 均为 UserContext
    /// 2. {field} operator value, 其中 {field} 为路由参数, 用户上下文的参数, 和来自 http 上下文参数
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="accessRule">访问规则</param>
    /// <returns></returns>
    private bool AuthorizeAccessRule(UserContext userContext, AccessRuleViewModel accessRule)
    {
        if (accessRule.AccessCount > 0 && accessRule.LimitAccessCount > 0 && accessRule.AccessCount >= accessRule.LimitAccessCount)
        {
            _logger.LogInformation($"user {userContext.UserName} access rule: {accessRule.Code} access count: {accessRule.AccessCount} greater than and equal limit access count: {accessRule.LimitAccessCount}, access denied!");
            return false;
        }

        if (!string.IsNullOrWhiteSpace(accessRule.Condition) && accessRule.Code is not null)
        {
            var conditions = accessRule.Condition.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
            for (int index = 0; index < conditions.Length; index++)
            {
                var condition = conditions[index];
                var values = condition.Split(AppSettings.CHAR_SPACE, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                var field = values[0];
                var result = false;
                if (field.StartsWith(AppSettings.CHAR_AT))
                {
                    result = MatchMethod(userContext, accessRule.Code, condition, values);
                }
                else if (values.Length >= 3 && field.StartsWith('{') && field.EndsWith('}'))
                {
                    result = MatchExpression(userContext, accessRule.Code, condition, values);
                }

                if (!result)
                {
                    return false;
                }

                _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule.Code} condition: {condition} passed!");
            }
        }

        _logger.LogInformation($"user {userContext.UserName} access rule: {accessRule.Code} condition: {accessRule.Condition} all passed!");
        return true;
    }
    /// <summary>
    /// 获取资源访问路径, action
    /// 资源访问路径包含了路由参数变量名, 即: /api/controller/action
    /// </summary>
    /// <returns></returns>
    private (string, string) GetPath()
    {
        //=controller
        var _controller = string.Empty;
        if (_routeValues.TryGetValue(CONTROLLER, out object? controller))
        {
            _controller = controller as string;
        }

        //=action
        var _action = string.Empty;
        if (_routeValues.TryGetValue(ACTION, out object? action))
        {
            _action = action as string;
        }

        ArgumentNullException.ThrowIfNull(_controller);
        ArgumentNullException.ThrowIfNull(_action);

        //=/controller/action
        var _path = AppSettings.CHAR_SLASH + _controller + AppSettings.CHAR_SLASH + _action;

        if (_request.Path.HasValue)
        {
            var _prefix = string.Empty;
            var path = _request.Path.Value.ToUpperInvariant();
            var index = path.IndexOf(_controller.ToUpperInvariant());
            if (path != AppSettings.CHAR_SLASH.ToString() && index > 1)
            {
                //前缀, api/odata 之类
                _prefix = _request.Path.Value[1..(index - 1)];
            }

            if (_path != _request.Path.Value)
            {
                if (_prefix != null && _request.Path.Value != AppSettings.CHAR_SLASH + _prefix + _path)
                {
                    //=/api/controller/action
                    _path = AppSettings.CHAR_SLASH + _prefix + _path;
                }
                else
                {
                    _path = _request.Path.Value;
                }
            }
        }

        return (_path, _action);
    }

    /// <summary>
    /// @ 开头的参数为调用内部方法进行访问授权, 方法的第一个参数不需要填写, 均为 UserContext
    /// @method params...
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="accessRule">访问规则</param>
    /// <param name="condition">当前条件</param>
    /// <param name="values">条件参数</param>
    /// <returns></returns>
    private bool MatchMethod(UserContext userContext, string accessRule, string condition, params string[] values)
    {
        var method = values[0];
        _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from method: {method}");

        var methodInfo = GetType().GetMethod(method);
        if (methodInfo is not null)
        {
            var parameters = new object[] { userContext };
            if (values.Length > 1)
            {
                parameters.AddRange(values[1..]);
            }

            var returnValue = methodInfo.Invoke(this, parameters);
            if (returnValue != null && returnValue is bool result)
            {
                if (!result)
                {
                    _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from method: {method}, execute result not passed, access denied!");
                }

                return result;
            }
        }

        _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from method: {method}, method not found or params not matched, access denied!");
        return false;
    }
    /// <summary>
    /// 变量构成的表达式
    /// 结构: {field} operator value, 其中 {field} 为路由参数, 用户上下文的参数, 和来自 http 上下文的参数
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="accessRule">访问规则</param>
    /// <param name="condition">当前条件</param>
    /// <param name="values">条件参数</param>
    /// <returns></returns>
    private bool MatchExpression(UserContext userContext, string accessRule, string condition, params string[] values)
    {
        //字段
        var field = values[0];
        //逻辑
        var operatorType = values[1].GetEnumValueByJsonName<OperatorType>();
        //预设值
        var fieldValue = string.Concat(values[2..]);

        _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from variable: {field}, operator: {operatorType}, setting value: {fieldValue}");

        //实际值
        //默认规则形如: field eq value, 根据 field 从请求参数获取参数值, 和 value 进行比较以判断结果
        string? value;

        //{} 参数来自路由或上下文用户信息
        //如: /api/users/getUserRoles/{userName} 次数的 userName 参数值来自路由, 因此规则可以写为: {userName} contains administrator, developer, 表示仅限 administrator 和 developer 用户
        if (_routeValues.ContainsKey(field) && _routeValues.TryGetValue(field, out object? routeValue))
        {
            value = routeValue?.ToString();
            _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from route variable: {field}, operator: {operatorType}, setting value: {fieldValue}, actual value: {value}");
        }
        else
        {
            //1. UserContext value
            value = userContext.GetPropertyValue<UserContext, string>(field);

            //2. HttpContext query option
            value ??= _httpContext.GetContextOption<string>(field);

            //3. HttpRequest value
            value ??= _request.GetPropertyValue<HttpRequest, string>(field);

            //4. HttpContext value
            value ??= _httpContext.GetPropertyValue<HttpContext, string>(field);

            _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from context variable: {field}, operator: {operatorType}, setting value: {fieldValue}, actual value: {value}");
        }

        value ??= string.Empty;

        //比较规则为: 实际值和预设值比较, 如果比较结果为 false, 则表示不满足条件
        var result = operatorType switch
        {
            OperatorType.Equal => value == fieldValue,
            OperatorType.NotEqual => value != fieldValue,
            OperatorType.GreaterThan => Convert.ToDecimal(value) > Convert.ToDecimal(fieldValue),
            OperatorType.GreaterThanAndEqual => Convert.ToDecimal(value) >= Convert.ToDecimal(fieldValue),
            OperatorType.LessThan => Convert.ToDecimal(value) < Convert.ToDecimal(fieldValue),
            OperatorType.LessThanAndEqual => Convert.ToDecimal(value) >= Convert.ToDecimal(fieldValue),
            OperatorType.Contains => value.Contains(fieldValue),
            OperatorType.StartsWith => value.StartsWith(fieldValue),
            OperatorType.EndsWith => value.EndsWith(fieldValue),
            OperatorType.In => fieldValue.Contains(value),
            OperatorType.NotIn => !fieldValue.Contains(value),
            _ => false
        };

        if (!result)
        {
            _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from variable: {field}, operator: {operatorType}, setting value: {fieldValue}, actual value: {value}, check result not passed, access denied!");
        }

        return result;
    }

    /// <summary>
    /// odata 查询表达式不允许出现 select 的 value 值
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="value">select 表达式预设值，为逗号分隔的字符串，表示不可被查询的列名，默认空, 或 *, 表示必须提供 select 选项, 不可以查询全部列</param>
    /// <returns></returns>
    private bool ODataNotAllowSelect(UserContext userContext, string? value = default)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: ODataNotAllowSelect (OData protocol not allow $select expression), parameter: {value}");

        //验证实际值
        var select = _request.GetRequestOption<string>(AppSettings.QUERY_OPTION_SELECT);
        if (string.IsNullOrWhiteSpace(select) || select == AppSettings.CHAR_STAR.ToString())
        {
            return false;
        }

        //验证预设值
        if (string.IsNullOrWhiteSpace(value) || value == AppSettings.CHAR_STAR.ToString())
        {
            //当预设值为全部列，意味实际值只要不为全部列即可
            return (select is not null) && select != AppSettings.CHAR_STAR.ToString();
        }

        //验证实际值其余情况
        return !select.ContainsAny(value, AppSettings.CHAR_COMMA);
    }
    /// <summary>
    /// odata 查询表达式不允许出现 expand 的 value 值
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="value">expand 表达式的预设值, 表示不能出现在 expand 中的选项</param>
    /// <returns></returns>
    private bool ODataNotAllowExpand(UserContext userContext, string? value = default)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: ODataNotAllowExpand (OData protocol not allow $expand expression), parameter: {value}");
        if (string.IsNullOrWhiteSpace(value))
        {
            return true;
        }

        var expand = _request.GetRequestOption<string>(AppSettings.QUERY_OPTION_EXPAND);
        if (!string.IsNullOrWhiteSpace(expand))
        {
            return !expand.ContainsAny(value, AppSettings.CHAR_COMMA);
        }

        return true;
    }
    /// <summary>
    /// 用户是否分配了某角色
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="roles">角色</param>
    /// <returns></returns>
    private bool UserInRoles(UserContext userContext, params string[] roles)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: UserInRoles (whether the user is assigned roles: {roles.ToString(AppSettings.DEFAULT_OUTER_SPLITER)})");
        return userContext.Roles?.Select(x => x.Name?.ToUpperInvariant())?.Intersect(roles.Select(x => x.ToUpperInvariant())).Any() ?? false;
    }
    /// <summary>
    /// 用户是否分配了某群组
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="groups">群组</param>
    /// <returns></returns>
    private bool UserInGroups(UserContext userContext, params string[] groups)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: UserInGroups (whether the user is assigned groups: {groups.ToString(AppSettings.DEFAULT_OUTER_SPLITER)})");
        return userContext.Groups?.Select(x => x.Name?.ToUpperInvariant())?.Intersect(groups.Select(x => x.ToUpperInvariant())).Any() ?? false;
    }
    /// <summary>
    /// 请求需匹配具体的 Http Method, 不区分大小写
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="httpMethod">Http Method</param>
    /// <returns></returns>
    private bool HttpMethod(UserContext userContext, string httpMethod)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: HttpMethod (whether the request http method is matched: {httpMethod})");
        return httpMethod.ToUpperInvariant().Contains(_request.Method.ToUpperInvariant());
    }
}